grammar myGrammar;

options {
    language = Java;
    output = AST;
    ASTLabelType = CommonTree;
}

@header {
    package compilers;
    import java.util.Vector;
    
    import compilers.condition.*;
    import compilers.output.*;
    import compilers.types.*;
    import compilers.stmt.*;
    import compilers.table.*;
    import compilers.node.*;
}

@members {
  boolean stdioIncluded = false;
  int whileCounter = 0;
  SymbolTable symbolTable = new SymbolTable();
  Program root = new Program();
  Stack<FunctionType> currentFunc = new Stack<FunctionType>();
}

@lexer::header {
    package compilers;
}

include: INCLUDE {stdioIncluded = true;};

smallc_program returns[Node root]
@after {
  $root = root;
  symbolTable.checkMain();
}: include? (typedef {root.addChild($typedef.parent);} 
	| funct_decl {root.addChild($funct_decl.parent);} 
	| funct {root.addChild($funct.parent);} 
	| var {root.addChild($var.parent);})* EOF!;

typedef returns[Typedef parent]
	@init {String name=""; Type newType = null;}:
	TYPEDEF type 
	(IDENT {name=$IDENT.text;newType=$type.type;}) 
	{$parent = new Typedef(newType, new Identifier(name)); symbolTable.addType(name, newType);} SEMI; 

funct_decl returns[Node parent]
@init {FunctionType t = null; String name;}: return_type IDENT {t = new FunctionType($return_type.type, new Identifier($IDENT.text)); name = $IDENT.text;} LPAR paramlist_decl[t]? RPAR SEMI {symbolTable.addFunction(name, t, input.LT(1)); symbolTable.checkFunctionParam(name, t, input.LT(1));  $parent=t;};

paramlist_decl [FunctionType t]
@init {Param tmp;}: (t1=type {t.addParameter(tmp=new Param($t1.type));} ((id1=IDENT) {tmp.addIdentifier(new Identifier($id1.text));} ('[' {$t1.type.setArray();} (i1=INTEGER {$t1.type.setArray(Integer.valueOf($i1.text));})? ']')?)?)
					(COMMA t2=type {t.addParameter(tmp=new Param($t2.type));} ((id2=IDENT) {tmp.addIdentifier(new Identifier($id2.text));} ('[' {$t2.type.setArray();} (i2=INTEGER {$t2.type.setArray(Integer.valueOf($i2.text));})? ']' )?)?)*;
				
funct returns[Stmt parent]
@init {FunctionType t = null; String name; }
@after {
  if(!t.hasReturn() && !(t.getReturnType() instanceof VoidType)) {
    new ErrorOutput(input.LT(1),"Function doesn't have a return");
  }
}: return_type IDENT {name = $IDENT.text; t = new FunctionType($return_type.type, new Identifier(name)); t.setBody(); symbolTable.addFunction(name, t, input.LT(1)); currentFunc.push(t);} LPAR {symbolTable.setScope(new Scope(symbolTable.getScope()));} paramlist[t]? RPAR (compound_stmt_func {t.addStatement($compound_stmt_func.parent); }) { symbolTable.checkFunctionParam(name, t, input.LT(1)); currentFunc.pop(); $parent=t; symbolTable.setScope(symbolTable.getScope().getParent());};

paramlist [FunctionType t]: (t1=type (id1=IDENT {symbolTable.getScope().addVariable($id1.text, $t1.type, input.LT(1)); t.addParameter(new Param($t1.type, new Identifier($id1.text)));}) ('[' {$t1.type.setArray();} (i1=INTEGER {$t1.type.setArray(Integer.valueOf($i1.text));})? ']')?) 
							(COMMA t2=type (id2=IDENT {symbolTable.getScope().addVariable($id2.text, $t2.type, input.LT(1)); t.addParameter(new Param($t2.type, new Identifier($id2.text)));}) ('[' {$t2.type.setArray();} (i2=INTEGER {$t2.type.setArray(Integer.valueOf($i2.text));})? ']')?)*;

var returns[Node parent]: type varlist[$type.type] {$parent = $varlist.parent;} SEMI!;

varlist[Type type] returns[Node parent]
@init {
VarList v = new VarList();
}: v1=varid[$type] {v.addVar($v1.parent, symbolTable.getScope(), input.LT(1));} (COMMA v2=varid[$type.clone()] {v.addVar($v2.parent, symbolTable.getScope(), input.LT(1));})* {$parent = v;};

varid[Type type] returns[Var parent]
@init {
int count = 0;
Var var = null;
Type tmp = $type;
Identifier ident = null;
} : 
(IDENT {var = new Var(tmp, ident=new Identifier($IDENT.text, input.LT(1), symbolTable.getScope()));tmp.setArray(0);}) 
(EQUAL 
	(condition {var = new Var(tmp, new Identifier($IDENT.text, input.LT(1), symbolTable.getScope()), $condition.parent);symbolTable.checkTypes(tmp, $condition.type, true, input.LT(1));}
	| array_init[var, count, tmp, ident])
)? {$parent = var;};

array_init[Var var, int count, Type tmp, Identifier ident]:
LBRACK e1=condition{var.addExpression($e1.parent); count++; symbolTable.checkTypes(tmp, $e1.type, true, input.LT(1));} 
(COMMA e2=condition {count++; var.addExpression($e2.parent); symbolTable.checkTypes(tmp, $e2.type, true, input.LT(1));})* 
RBRACK {symbolTable.checkArrayInit(count, ident, input.LT(1)); var.initArray();};


type returns[Type type]
@init { boolean constant=false; }: 
	(CONST {constant=true;})? 
	(INT {$type = new IntType(constant); }| 
	FLOAT {$type = new FloatType(constant);}| 
	CHAR {$type = new CharType(constant);}|
	IDENT {$type = symbolTable.resolveType($IDENT.text, input.LT(1)); if($type == null) {new ErrorOutput(input.LT(1), "Type doesn't exist.");}});


return_type returns [Type type]: t1=type {$type = $t1.type;} | VOID {$type = new VoidType(true);};

varstmt returns [Stmt parent]
@init{Stmt vs = new Stmt();}: (var {vs.addNode($var.parent);} | stmt {vs.addNode($stmt.parent);})* {$parent=vs;};

compound_stmt returns [Stmt parent]: LBRACK {symbolTable.setScope(new Scope(symbolTable.getScope()));} varstmt {$parent=$varstmt.parent;} RBRACK {symbolTable.setScope(symbolTable.getScope().getParent());};

compound_stmt_func returns [Stmt parent]: LBRACK varstmt {$parent=$varstmt.parent;}RBRACK;

stmt returns [Stmt parent]: 
	cond_stmt {$parent = $cond_stmt.parent;} | 
    while_stmt {$parent = $while_stmt.parent;} | 
    'break' {if(whileCounter == 0) {new ErrorOutput(input.LT(1), "Break statement not in while loop.");} $parent = new Keyword("break");}SEMI | 
    'continue'  {if(whileCounter == 0) {new ErrorOutput(input.LT(1), "Continue statement not in while loop.");}$parent = new Keyword("continue");}SEMI |
    'return' SEMI {$parent = new Return(new VoidType(true)); currentFunc.peek().checkReturnType(new VoidType(true), (Return)$parent, input.LT(1)); } | 
    'return' condition {$parent = new Return($condition.parent); currentFunc.peek().checkReturnType($condition.type, (Return)$parent, input.LT(1));} SEMI |
    expr {$parent = $expr.parent;} SEMI |
    funct {$parent = $funct.parent;};

cond_stmt returns [If parent]
@init{If tmp = null;}: 'if' LPAR condition RPAR LBRACK {symbolTable.setScope(new Scope(symbolTable.getScope()));} s1=varstmt RBRACK {symbolTable.setScope(symbolTable.getScope().getParent());tmp=new If($condition.parent, $s1.parent);} (options {greedy=true;}:'else' LBRACK s2=varstmt RBRACK {tmp.setElse($s2.parent);})? {$parent = tmp;};

while_stmt returns[Stmt parent]: 'while' LPAR {whileCounter++;} condition RPAR LBRACK {symbolTable.setScope(new Scope(symbolTable.getScope()));} varstmt RBRACK {symbolTable.setScope(symbolTable.getScope().getParent());$parent = new While($condition.parent, $varstmt.parent, input.LT(1)); whileCounter--;};

printf returns [IntType type, Printf parent]: 'printf' LPAR STRING_LITERAL {$parent = new Printf($STRING_LITERAL.text); $type = new IntType(true);} (COMMA condition {$parent.addNode($condition.parent);})* RPAR {$parent.checkParameters(input.LT(1), stdioIncluded);};

scanf returns [IntType type, Scanf parent]: 'scanf' LPAR STRING_LITERAL {$parent = new Scanf($STRING_LITERAL.text); $type = new IntType(true);} (COMMA ident {$parent.addIdentifier(new Identifier($ident.text));})* RPAR {$parent.checkParameters(input.LT(1), stdioIncluded);};

funccall returns [Type type, FuncCall parent]
@init { Vector<Param> parameters = new Vector<Param>(); }: 
IDENT {$type = symbolTable.resolveFunction($IDENT.text, input.LT(1)).getReturnType(); symbolTable.checkFunctionBody(input.LT(1), $IDENT.text);} 
LPAR (e1=condition {parameters.add(new Param($e1.parent));} 
(COMMA e2=condition {parameters.add(new Param($e2.parent));})*)? 
{$parent = new FuncCall(new Identifier($IDENT.text), parameters, symbolTable.resolveFunction($IDENT.text, input.LT(1))); symbolTable.resolveFunction($IDENT.text, input.LT(1)).checkParameters(parameters, input.LT(1), true);}RPAR;

expr returns [Type type, Stmt parent] 
@init {Type c = null;}: 
	ident EQUAL e1=condition {$type = $e1.type; symbolTable.checkTypes($ident.type, $e1.type, false, input.LT(1)); $parent=new Var($ident.type, new Identifier($ident.name, input.LT(1), symbolTable.getScope()), $e1.parent);}
	| condition {$type = $condition.type; $parent = $condition.parent;};
	
condition returns [Type type, Condition parent]: disjunction {$type = $disjunction.type; $parent = $disjunction.parent;} (options {greedy=true;}: 
		QMARK e1=condition {$type = $e1.type;} DPOINT e2=condition {$type = $e2.type; $parent = new Condition($disjunction.parent, $e1.parent, $e2.parent);})*;

disjunction returns [Type type, Term parent]: c1=conjunction {$type = $c1.type; $parent = $c1.parent;} 
		('||' c2=conjunction {$type = $c2.type; $parent = new Term($c1.parent, $c2.parent, "||");})*;

conjunction returns [Type type, Term parent]: c1=comparison {$type = $c1.type; $parent = $c1.parent;} 
		('&&' c2=comparison {$type = $c2.type;$parent = new Term($c1.parent, $c2.parent, "&&");})*;

comparison returns [Type type, Term parent]: r1=relation {$type = $r1.type; $parent = $r1.parent;} 
		( '!=' r3=relation {$type = $r3.type; $parent = new Term($r1.parent, $r3.parent, "!=");}
		| '==' r2=relation {$type = $r2.type; $parent = new Term($r1.parent, $r2.parent, "==");})*;

relation returns [Type type, Term parent]: s1=sum {$type = $s1.type; $parent = $s1.parent;} 
		('<' s2=sum {$type = $s2.type;$parent = new Term($s1.parent, $s2.parent, "<");} 
		| '<=' s4=sum {$type = $s4.type;$parent = new Term($s1.parent, $s4.parent, "<=");}
		| '>' s3=sum {$type = $s3.type;$parent = new Term($s1.parent, $s3.parent, ">");}
		| '>=' s5=sum {$type = $s5.type;$parent = new Term($s1.parent, $s5.parent, ">=");})*;

sum returns [Type type, Term parent]: t1=term {$type = $t1.type; $parent = $t1.parent;} 
	('+' t2=term {$type = $t2.type; if($parent instanceof Term) { $parent = new Term($parent, $t2.parent, "+"); } else { $parent = new Term($t1.parent, $t2.parent, "+");}}
	| '-' t3=term {$type = $t3.type; if($parent instanceof Term) { $parent = new Term($parent, $t3.parent, "-"); } else {$parent = new Term($t1.parent, $t3.parent, "-");}})*;

term returns [Type type, Term parent]: f1=factor {$type = $f1.type; $parent = $f1.parent;} 
	('*' f2=factor {$type = $f2.type; if($parent instanceof Term) { $parent = new Term($parent, $f2.parent, "*"); } else { $parent = new Term($f1.parent, $f2.parent, "*"); }} 
	| '/' f3=factor {$type = $f3.type; if($parent instanceof Term) { $parent = new Term($parent, $f3.parent, "/"); } else { $parent = new Term($f1.parent, $f3.parent, "/"); }} 
	| '%' f4=factor {$type = $f4.type; if($parent instanceof Term) { $parent = new Term($parent, $f4.parent, "\%"); } else { $parent = new Term($f1.parent, $f4.parent, "\%"); }})*;

factor returns[Type type, Factor parent]: 
	'!' f1=factor {$type = $f1.type; $parent = new Factor($f1.type, "!");} 
	| '-' f2=factor {$type = $f2.type; $parent = new Factor($f2.type, "-");} 
	| LPAR p2=primary_type RPAR f3=factor {$type = $p2.type; $f3.type.setCast($type); $parent = new Factor($f3.type, $p2.type);}
	| LPAR condition RPAR {$type = $condition.type; $parent = new Factor($condition.parent,$condition.type);}
	| p1=primary {$type = $p1.type; $parent = $p1.type;}
	| expr_f {$type = $expr_f.type; $parent = $expr_f.parent;};
	
primary_type returns[Type type]: 
	FLOAT {$type = new FloatType(true);} 
	| INT {$type = new IntType(true);}
	| CHAR {$type = new CharType(true);};
	
expr_f returns [Type type, Factor parent] : 
	 funccall  {$parent = $funccall.parent; $type = $funccall.type;} 
	| printf {$parent=$printf.parent; $type=$printf.type;} 
	| scanf {$parent=$scanf.parent; $type=$scanf.type;};

primary returns[Type type]: 
	REAL {$type = new FloatType(Float.valueOf($REAL.text), true);} 
	| INTEGER {$type = new IntType(Integer.valueOf($INTEGER.text), true);}
	| CHAR_LITERAL {$type = new CharType($CHAR_LITERAL.text.charAt(0), true);} 
	| ident {$type=$ident.type;};

ident returns[String name, Type type]: 
IDENT {$name = $IDENT.text; $type = symbolTable.resolveVariable($IDENT.text, input.LT(1), false); $type.setIdentifier(new Identifier($name, input.LT(1), symbolTable.getScope()));};

LBRACK: '{';

RBRACK: '}';

LPAR: '(';

RPAR: ')';

INT: 'int';

FLOAT: 'float';

CHAR: 'char';

VOID: 'void';

CONST: 'const';

INCLUDE: '#include <stdio.h>';

COMMA: ',';

DPOINT: ':';

SEMI: ';';

EQUAL: '=';

TYPEDEF: 'typedef';

QMARK: '?';

STRING_LITERAL: 
    '"'
    { StringBuilder b = new StringBuilder(); }
    ( '"' '"'       { b.appendCodePoint('"');} | c=~('"'|'\r'|'\n')  { b.appendCodePoint(c);} )*
    '"'
    { setText(b.toString()); };

CHAR_LITERAL: '\'' . '\'' {setText(getText().substring(1,2));};

fragment LETTER: ('a'..'z' | 'A'..'Z') ;
fragment DIGIT: '0'..'9';
INTEGER: DIGIT+;
REAL: DIGIT+ '.' DIGIT+;
IDENT: (LETTER | '_') (LETTER | DIGIT | '_' | '[' | ']')*;
WS: (' ' | '\t' | '\n' | '\r' | '\f')+ {$channel = HIDDEN;};
COMMENT: '//' .* ('\n'|'\r') {$channel = HIDDEN;};
MULTILINE_COMMENT: '/*' .* '*/' {$channel = HIDDEN;} ;
MULTILINE_EOF: '/*' (~('*') ~('/'))* {$channel = HIDDEN;};